package com.acme.bankapp.service.bank;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.util.Set;

import com.acme.bankapp.domain.bank.Account;
import com.acme.bankapp.domain.bank.Bank;
import com.acme.bankapp.domain.bank.Client;
import com.acme.bankapp.domain.bank.exception.ClientExistsException;
import com.acme.service.NeatPrinter;

/**
 * Bank service class.
 * 
 */
public class BankService {
	private static final int START_NUMBERING_FROM = 1;

	private static final String ACCOUNT_LIST_TITLE = "ACCOUNT LIST";
	private static final String BALANCE_TITLE = "BALANCE";
	private static final String MAX_AMOUNT_TO_WITHDRAW_TITLE = "MAX. AMOUNT TO WITHDRAW";
	private static final String CLIENT_LIST_TITLE = "CLIENT LIST";

	/**
	 * Prints balance for every account of every client of the bank.
	 * 
	 * @param bank
	 *            bank
	 */
	public static void printBalance(Bank bank) {
		NeatPrinter.printTitle(BALANCE_TITLE);
		for (Client client : bank.getClients()) {
			System.out.println("*** Client " + client.getClientSalutation()
					+ " ***");
			for (Account account : client.getAccounts()) {
				System.out.println(" Balance $: " + account.getBalance());
			}
		}
		System.out.println();
	}

	/**
	 * Prints maximum amount to withdraw for every account of every client of
	 * the bank.
	 * 
	 * @param bank
	 *            bank
	 */
	public static void printMaximumAmountToWithdraw(Bank bank) {
		NeatPrinter.printTitle(MAX_AMOUNT_TO_WITHDRAW_TITLE);
		for (Client client : bank.getClients()) {
			System.out.println("*** Client " + client.getClientSalutation()
					+ " ***");
			for (Account account : client.getAccounts()) {
				System.out.println(" Max. amount to withdraw $: "
						+ account.maximumAmountToWithdraw());
			}
		}
		System.out.println();
	}

	/**
	 * Prints all bank's clients.
	 * 
	 * @param bank
	 *            bank
	 */
	public static void printAllClients(Bank bank) {
		printAllClients(System.out, bank);
	}

	/**
	 * Prints all bank's clients to the print stream.
	 * 
	 * @param output
	 *            print stream
	 * @param bank
	 *            bank
	 */
	public static void printAllClients(PrintStream printStream, Bank bank) {
		NeatPrinter.printTitle(printStream, CLIENT_LIST_TITLE);
		int number = START_NUMBERING_FROM;
		for (Client client : bank.getClients()) {
			printStream.println(number + ". " + client);
			number++;
		}
		printStream.println();
	}

	/**
	 * Print all bank's accounts.
	 * 
	 * @param bank
	 *            bank
	 */
	public static void printAllAccounts(Bank bank) {
		NeatPrinter.printTitle(ACCOUNT_LIST_TITLE);
		int number = START_NUMBERING_FROM;
		for (Account account : bank.getAccounts()) {
			System.out.println(number + ". " + account.toString());
			number++;
		}
		System.out.println();
	}

	/**
	 * Serialized bank object and saves it to file.
	 * 
	 * @param bank
	 *            bank to serialize
	 * @param file
	 *            output file
	 * @throws IOException
	 *             thrown, if I/O error occurs while saving to file
	 */
	public static void saveBank(Bank bank, File file) throws IOException {
		try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(
				new FileOutputStream(file))) {
			objectOutputStream.writeObject(bank.getClients());
		}
	}

	/**
	 * Reads serialized bank object from file and deserializes it.
	 * 
	 * @param file
	 *            input file
	 * @return deserialized bank object
	 * @throws IOException
	 *             thrown, if I/O error occurs while reading from file
	 */
	@SuppressWarnings("unchecked")
	public static Bank readBank(File file) throws IOException {
		try (ObjectInputStream objInputStream = new ObjectInputStream(
				new FileInputStream(file))) {
			Bank bank = new Bank();			
			Set<Client> clients = (Set<Client>) objInputStream.readObject();
			for (Client client : clients) {
				bank.addClient(client);
			}
			return bank;
		} catch (ClassNotFoundException | IOException | ClassCastException
				| ClientExistsException e) {
			throw new IOException();
		}
	}
}
